/*
 * @Author: soso
 * @Date: 2022-01-09 11:51:45
 * @LastEditTime: 2022-03-09 15:50:35
 * @LastEditors: Please set LastEditors
 * @Description: 通讯基本类
 * @FilePath: /sync-client/comm/comm.go
 */
package comm

import (
	"bytes"
	"errors"
	"fmt"
	"gomeshcli/comm/defines"
	"gomeshcli/sconfig"
	"io"
	"log"
	"net"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"gitee.com/kimsoso/go-utils/sonet"
	"gitee.com/kimsoso/go-utils/utils"
	"github.com/vmihailenco/msgpack"
)

// 发送本地文件删除，目录删除命令给远程节点
func SendLocalDelete(realfilePaths []string, host string) error {
	// 建立连接
	conn := timeoutConn(host)

	// 转换为相对路径
	relPaths := []string{}
	for _, path := range realfilePaths {
		relPath, err := utils.RelativePath(sconfig.SConfig.WatchDir, path)
		if err != nil {
			return err
		}
		relPaths = append(relPaths, relPath)
	}

	relPathsBuf := strings.Join(relPaths, "|")
	cmd := fmt.Sprintf("%s:%s", defines.CLI_DELFILES, relPathsBuf)

	err := sonet.Write(conn, []byte(cmd))
	if err != nil {
		return err
	}

	_, buf, err := sonet.Read(conn)
	if err != nil {
		return err
	}

	if string(buf) == defines.COMM_OK {
		return nil
	} else {
		return errors.New("server process error")
	}
}

// 发送本地存在的文件给远程节点
func SendLocalExistFile(sfiles []*sconfig.Sfile, host string) (finishedFiles map[string]bool, err error) {
	finishedFiles = map[string]bool{}
	// 建立连接
	conn := timeoutConn(host)
	defer conn.Close()

	for _, sfile := range sfiles {
		relFilePath, _ := utils.RelativePath(sconfig.SConfig.WatchDir, sfile.Path)

		var fsize int64
		var isDir uint8
		if sfile.IsDir {
			fsize = 0
			isDir = 1
		} else {
			fsize = sfile.Size
			isDir = 0
		}

		cmd := fmt.Sprintf("%s:%s:%d:%d", defines.COMM_SendNewFile, relFilePath, fsize, isDir)

		err = sonet.Write(conn, []byte(cmd))
		if err != nil {
			return
		}

		var buf []byte
		_, buf, err = sonet.Read(conn)
		if err != nil {
			return
		}

		if sfile.IsDir || sfile.Size == 0 {
			if string(buf) == defines.COMM_OK {
				finishedFiles[sfile.Path] = true
				continue
			} else {
				return
			}
		}

		if bytes.HasPrefix(buf, []byte(defines.COMM_ReadyReceiveFile)) {
			//发送文件
			utils.FPN("发送文件:", relFilePath)
			params := bytes.Split(buf, []byte(":"))
			offset, _ := strconv.Atoi(string(string(params[2])))

			_, err = sonet.SendFileData(conn, sfile.Path, int64(offset), 0, nil)

			if err == nil {
				_, buf, err = sonet.Read(conn)
				if err != nil {
					return
				} else {
					if string(buf) == defines.COMM_OK {
						finishedFiles[sfile.Path] = true
						continue
					} else {
						return
					}
				}
			} else {
				return
			}
		} else {
			return
		}
	}
	return
}

/**
 * @description: 得到服务器群文件列表
 * @param {string} remoteHost
 * @return {*}
 */
func GetFileList(remoteHost string) (fileList []defines.Cfile, err error) {
	// 建立连接
	conn, err := net.Dial("tcp", remoteHost)
	if err != nil {
		return nil, err
	}

	err = sonet.Write(conn, []byte(defines.CLI_FILE_LIST))
	if err != nil {
		return nil, err
	}
	buf := make([]byte, defines.BufSize)
	fileListString := []byte{}
	for {
		n, err := conn.Read(buf)
		if err == io.EOF {
			log.Println(" Receive file list finished!")
			break
		}
		if err != nil {
			log.Println("Net read err:", err)
			return nil, err
		}
		fileListString = append(fileListString, buf[:n]...)
	}

	err = msgpack.Unmarshal(fileListString, &fileList)
	if err != nil {
		return nil, err
	}
	return nil, nil
}

/**
 * @description: 同步文件
 * @param {string} targetDir
 * @return {*}
 */
func SyncDir(targetDir string) {
	ss := NewSsyncDirs(PathOfSync)

	ss.GetDirFiles(targetDir)

	conn := timeoutConn(sconfig.SConfig.GetHostPort())
	defer conn.Close()

	err := sonet.Write(conn, []byte(defines.CLI_SYNCFILE+":"+targetDir))
	if err != nil {
		log.Println("write to host:", sconfig.SConfig.GetHostPort(), " error", err)
		return
	}

	_, cmd, err := sonet.Read(conn)
	if err != nil {
		log.Println("Read host COMM_OK:", sconfig.SConfig.GetHostPort(), " error", err)
		return
	}

	if string(cmd) == defines.COMM_OK {
		buf, _ := msgpack.Marshal(ss.Files)
		err = sonet.Write(conn, buf)
		if err != nil {
			utils.LPN("send syncfiles error:", err)
			return
		}
		utils.FPN("write done!")

		_, cmd, err = sonet.Read(conn)
		if err != nil {
			utils.FPN("remoteBuf read error:", err)
			return
		}

		ss.InitFiles()
		err = msgpack.Unmarshal(cmd, &ss.Files)
		if err != nil {
			log.Println("Unmarshal error:", err)
			return
		}

		// 存储实际路径
		for _, file := range ss.Files {
			file.Path = filepath.Clean(filepath.Join(sconfig.SConfig.WatchDir, targetDir, file.Path))
		}

		// 同步磁盘
		if len(ss.Files) > 0 {
			ss.writeFilesSEQ()
			ss.sendFilesSEQ()
		}
	}
}

func SendRename(fromRealFilePath, toRealFilePath string) error {
	from := ToRelSvrPath(fromRealFilePath)
	to := ToRelSvrPath(toRealFilePath)

	conn := timeoutConn(sconfig.SConfig.GetHostPort())
	defer conn.Close()

	err := sonet.Write(conn, []byte(fmt.Sprintf("%s:%s:%s", defines.CLI_RNMFILE, from, to)))
	if err != nil {
		return nil
	}

	_, buf, err := sonet.Read(conn)
	if err == nil && string(buf) == defines.COMM_OK {
		return nil
	} else {
		return errors.New("node operate failed")
	}
}

func timeoutConn(host string) (conn net.Conn) {
	dialer := net.Dialer{Timeout: time.Second * defines.SocketTimeout}
	var err error
	for {
		conn, err = dialer.Dial("tcp", host)
		if err == nil {
			return
		}
		log.Println("cant connect " + host + ", retry in 3 secs")
		time.Sleep(time.Second * 3)
	}
}

func ToRelSvrPath(filePath string) string {
	if !filepath.IsAbs(filePath) {
		filePath, _ = filepath.Abs(filePath)
	}

	basePath := sconfig.SConfig.WatchDir
	if !filepath.IsAbs(sconfig.SConfig.WatchDir) {
		basePath, _ = filepath.Abs(sconfig.SConfig.WatchDir)
	}

	path, _ := filepath.Rel(basePath, filePath)
	return path
}
